﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.IO;


///////////// TODO: Add a CookieReceived event.

namespace HNPL.Sockets
{
    public enum UdpResult
    {
        BufferSubmited,
        BufferReceived,
        Canceled
    }
    
    public class UdpSocketTool
    {
        private UdpClient client;

        public event EventHandler<UdpCookiePiece> UdpPacketReceived;
        //public event EventHandler<UdpCookie> CookieReceived;

        private void null_packetReceived(object sender, UdpCookiePiece e)
        {
        }

        private void null_cookieReceived(object sender, UdpCookie e)
        {
        }

        private UdpSocketTool(int port)
        {
            client = new UdpClient(port);
            UdpPacketReceived += new EventHandler<UdpCookiePiece>(null_packetReceived);
            //CookieReceived += new EventHandler<UdpCookie>(null_cookieReceived);
        }

        private UdpSocketTool()
        {
            client = new UdpClient();
            UdpPacketReceived += new EventHandler<UdpCookiePiece>(null_packetReceived);
            //CookieReceived += new EventHandler<UdpCookie>(null_cookieReceived);
        }

        public void SendPacket(byte[] data, IPEndPoint endPoint)
        {
            client.Close();
            client.Connect(endPoint);
            client.Send(data, data.Length);
        }

        public UdpResult BeginSendCookie(IPEndPoint server, byte[] totalBuffer, short pieceID)
        {
            int opID = new Random().Next();
            Log.Info("Starting operation, ID: " + opID.ToString("X"));
            BinaryReader reader = new BinaryReader(new MemoryStream(totalBuffer));
            Task t = Task.Factory.StartNew(delegate
            {
                start:
                int n = totalBuffer.Length / 1300;
                client.Connect(server);
                client.Send(BitConverter.GetBytes(n), 4);
                
                //Check for OK signal
                /*byte[] ok = client.Receive(ref server);
                if (ok[0] == 0x1)
                    client.Send(BitConverter.GetBytes(pieceID), sizeof(short));
                else
                    return;*/

                for (int i = 0; i < n; i++)
                {
                    UdpCookiePiece piece = new UdpCookiePiece(pieceID, (short)i, reader.ReadBytes(1300));
                    byte[] b = piece.ToArray();
                    client.Send(b, b.Length);
                    Log.Info("Sending cookie [" + pieceID.ToString() + "] piece, Number: " + i.ToString());
                }

                //Check for server response
                byte[] done = client.Receive(ref server);
                if (done[0] != 0x1)
                    goto start;

                Log.Info("Operation [" + opID.ToString("X") + "] has been completed.");
            });

            if (t.IsCanceled)
            {
                Log.Error("Operation [" + opID.ToString("X") + "] has been canceled.");
                return UdpResult.Canceled;
            }
            return UdpResult.BufferSubmited;
        }

        /*
        private void WaitForCookie(int nPieces, short cId, IPEndPoint ep)
        {
            Task.Factory.StartNew(delegate
            {
                UdpCookie cookie = new UdpCookie(cId);
                for (int i = 0; i < nPieces; i++)
                {
                    byte[] data = client.Receive(ref ep);
                    UdpCookiePiece piece = UdpCookiePiece.Read(data);

                    cookie.AddPiece(piece);
                }
                CookieReceived(this, cookie);
                Log.Info("Cookie has been received!");
            });
            BeginToListen();
        }*/

        private void CallbackReceive(IAsyncResult ar)
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, 5587);
            byte[] data = client.EndReceive(ar, ref ep);

            /*if (data.Length == 4)
            {
                client.Send(new byte[] { 0x1 }, 1, ep);
                byte[] id = client.Receive(ref ep);
                Log.Info("Waiting for cookie");
                WaitForCookie(BitConverter.ToInt32(data, 0), BitConverter.ToInt16(id, 0), ep);
                goto end;
            }*/



            UdpPacketReceived(this, UdpCookiePiece.Read(data));
            Log.Info("UDP packet received.");

            client.BeginReceive(CallbackReceive, null);
        //end:
            return;
        }

        public void BeginToListen()
        {
            client.BeginReceive(CallbackReceive, null);   
        }

        public static UdpSocketTool CreateServerTool(int port)
        {
            return new UdpSocketTool(port);
        }

        public static UdpSocketTool CreateClientTool()
        {
            return new UdpSocketTool();
        }
    }
}
